package com.achao.usercenter.aop;

import com.achao.usercenter.annotation.LogIgnore;
import com.achao.usercenter.annotation.enums.LogIgnoreEnum;
import com.achao.usercenter.dto.IgnoreParamDTO;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author achao
 * @description 日志切面
 * @date 2024/4/19 10:19
 */

@Slf4j
@Aspect
@Order(2)
@Component
public class LogAop {

    /**
     * 系统白名单
     */
    @Value("${achao.sys.whiteList}")
    private String[] whiteList;

    @Autowired
    private HttpServletRequest request;

    @Pointcut("execution(* com.achao.usercenter.controller..*(..))")
    public void authBefore() {
    }

    @Around("authBefore()")
    public Object beforeControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {

        // 过滤白名单
        String requestURI = request.getRequestURI();
        if (filterWhiteList(requestURI)) {
            return joinPoint.proceed();
        }

        long startTime = System.currentTimeMillis();
        Object[] args = joinPoint.getArgs();
        String methodName = joinPoint.getSignature().toShortString();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 参数列表
        Parameter[] parameters = method.getParameters();
        List<IgnoreParamDTO> ignoreParaList = new ArrayList<>();
        getParaList(parameters, ignoreParaList);


        // 过滤请求参数
        String inputJson = toJson(args, ignoreParaList);
        log.info("\n【用户中心】\n" +
                "请求地址：{} \n" +
                "方法名：{}\n" +
                "入参：{}", getRequestURL(), methodName,inputJson);
        Object result = joinPoint.proceed();
        String outputJson = JSON.toJSONString(result);
        long endTime = System.currentTimeMillis();
        log.info("\n【用户中心】\n" +
                "出参：{}\n" +
                "响应时间：{}", outputJson,endTime - startTime);

        return result;

    }

    /**
     * 获取需要打印日志的入参
     * 忽略带有@LogAopIgnore注解的入参
     *
     * @param parameters
     * @param ignoreParaList
     */
    private void getParaList(Parameter[] parameters, List<IgnoreParamDTO> ignoreParaList) {
        for (Parameter parameter : parameters) {
            Annotation[] annotations = parameter.getAnnotations();
            String parameterName = parameter.getName().toLowerCase();
            for (Annotation annotation : annotations) {
                if (annotation instanceof LogIgnore) {
                    IgnoreParamDTO ignoreParamDTO = getIgnoreParamDTO((LogIgnore) annotation, parameterName);
                    ignoreParaList.add(ignoreParamDTO);
                }
            }
        }
    }

    private static IgnoreParamDTO getIgnoreParamDTO(LogIgnore annotation, String parameterName) {
        IgnoreParamDTO ignoreParamDTO = new IgnoreParamDTO();
        LogIgnoreEnum model = annotation.model();
        String name = annotation.name();
        Class<?> clazz = annotation.clazz();

        ignoreParamDTO.setParamName(parameterName);
        ignoreParamDTO.setParamType(clazz);
        ignoreParamDTO.setLogIgnoreEnum(model);

        if (LogIgnoreEnum.NAME == model) {
            ignoreParamDTO.setParamName(name);
        }
        return ignoreParamDTO;
    }

    /**
     * 过滤白名单
     *
     * @param requestURI
     * @return
     */
    private boolean filterWhiteList(String requestURI) {
        for (String regex : whiteList) {
            if (matchingPath(regex, requestURI)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 匹配路径
     *
     * @param regex
     * @param path
     * @return
     */
    private boolean matchingPath(String regex, String path) {
        if (regex.contains("**")) {
            regex = regex.substring(0, regex.length() - 3) + ".*";
            Pattern pattern = Pattern.compile(regex);
            Matcher matcher = pattern.matcher(path);
            return matcher.matches();
        } else {
            return path.equals(regex);
        }

    }

    /**
     * 转换JSON字符串,并过滤携带LogAopIgnore注解的参数
     *
     * @param args
     * @param ignoreParaList
     * @return
     */
    private String toJson(Object[] args, List<IgnoreParamDTO> ignoreParaList) {
        args = Arrays.stream(args).filter(o -> {
            boolean flag = true;
            for (IgnoreParamDTO ignoreParamDTO : ignoreParaList) {
                if (o instanceof HttpServletRequest) {
                    return false;
                }
                String simpleName = o.getClass().getSimpleName().toLowerCase();
                LogIgnoreEnum logIgnoreEnum = ignoreParamDTO.getLogIgnoreEnum();
                if (LogIgnoreEnum.CLASS == logIgnoreEnum) {
                    if (simpleName .equals(ignoreParamDTO.getParamType().getSimpleName().toLowerCase())) {
                        flag = false;
                    }
                }

                if (LogIgnoreEnum.NAME == logIgnoreEnum || LogIgnoreEnum.SIMPLE == logIgnoreEnum) {
                    if (simpleName.equals(ignoreParamDTO.getParamName().toLowerCase())) {
                        flag = false;
                    }
                }

                return flag;
            }
            return flag;
        }).toArray();
        return JSON.toJSONString(args);
    }

    /**
     * 获取请求路径
     *
     * @return
     */
    private String getRequestURL() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            return request.getRequestURL().toString();
        }
        return "URL not available";
    }
}
